<?php
/**
 * Humanize View Helper
 * Helps make view data more readable to the average human
 * I owe much of the work here to the django humanize template tags
 *
 * @package     Visi
 * @subpackage  View Helpers
 * @author      Luke Visinoni <luke.visinoni@gmail.com>
 * @copyright   (c) 2011 Coalition Technologies <joel@coalitiontechnologies.com>
 * @version     $Id$
 */
class Visi_View_Helper_Humanize extends Zend_View_Helper_Abstract {

    const DAY_FORMAT = 'M d, Y';
    
    const TIME_FORMAT = 'g:ia';
    
    const DAYANDTIME_FORMAT = 'M d, Y \a\t g:ia';
    
    // I wonder if there will be issues with this when I go international ;) hee hee
    const DATE_FORMAT = 'm/d/Y h:i:sa';
    
    public function humanize() {
    
        return $this;
    
    }
    
    /**
     * Converts an integer to its ordinal as a string. 1 is '1st', 2 is '2nd', etc.
     * @todo Optionally humanize number as well and default to true. 1000 is 1,000th, etc.
     */
    public function ordinal($value) {
	
        $value = (integer) $value;
        $ord = array('th','st','nd','rd','th','th','th','th','th','th');
        // special cases
        if (in_array($value % 100, array(11, 12, 13))) return sprintf("%d%s", $value, $ord[0]);
        return sprintf('%d%s', $value, $ord[$value % 10]);
    
    }
    
    /**
     * Converts a large integer to a friendly text representation. Works best for
     * numbers over 1 million. For example, 1000000 becomes '1.0 million', 1200000
     * becomes '1.2 million' and '1200000000' becomes '1.2 billion'.
     *
     * For now, don't send numbers larger than 2 billion to it.
     */
    public function intword($value) {
    
        $value = (integer) $value;
        if ($value < 1000000) {
            return $value;
        }
	if ($value < 1000000000) {
            $new_value = $value / 1000000.0;
            return sprintf("%.1f million", $new_value);
        }
	if ($value < 1000000000000) {
            $new_value = $value / 1000000000.0;
            return sprintf("%.1f billon", $new_value);
        }
        /** Apparently, php doesn't support numbers this big... lame. I'm sure I could find
	        a work-around, but it's not a huge deal atm
	if ($value < 1000000000000000) {
            $new_value = $value / 1000000000000.0;
            return sprintf("%.1f trillion", $new_value);
        }
        */
	return $value;
    
    }
    
    /**
     * Converts a large integer to a friendly text representation. Works best for
     * numbers over 1 million. For example, 1000000 becomes '1.0 million', 1200000
     * becomes '1.2 million' and '1200000000' becomes '1.2 billion'.
     *
     * Because PHP can't work with numbers larger than 2 billion, I use strings instead,
     * this is proving to be VERY difficult to do. I'll get it, but I'm done messin with it for
     * now
     * @todo: finish the string work-around
    public function intword($value) {
	
        $str = (string) $value;
        $digits = strlen($str);
        // if less than a billion
        if ($digits < 10) {
            // we can work with numbers this large
            $new_value = ((int) $value) / 1000000.0;
            return sprintf("%.1f million", $new_value);
        }
        if ($digits < 13) { 
            // we can't work with numbers larger than 2 bil, so we need to do some trickery
            // find which position the dot should be
            $dotpos = $digits - 10 +1;
            // find how many digits we're going to show (how many past 10, which is a billion)
            
            return $showing;
            // find the first part
            $pt1 = substr($str, 0, $dotpos);
            // and the second part
            $pt2 = substr($str, strlen($pt1), ($showing-strlen($pt1)));
            // convert to float
            $float = (float) "$pt1.$pt2";
            return sprintf("%.1f billion", $float);
            
        }
    
    }
     */
	
    /**
     * Returns a "humanized" day - today, tomorrow, yesterday if relevant,
     * otherwise it returns the date in $format format
     * 
     * I am sure this is not the best way to do this, but it works
     */
    public function naturalDay($timestamp = null, $format = null) {
    
    	if (is_null($timestamp)) $timestamp = time();
    	if (is_null($format)) $format = self::DAY_FORMAT;
        if (!is_numeric($timestamp)) $timestamp = strtotime($timestamp);
    	
    	$oneday = 60*60*24;
    	$today = strtotime('today');
    	$tomorrow = $today + $oneday;
    	$yesterday = $today - $oneday;
    	// if time is 12:00 yesterday or more
    	if ($timestamp >= $yesterday) {
            // if time is less than 12:00 the day after tomorrow
            if ($timestamp < $tomorrow + $oneday && $timestamp > $today) {
                // if time is less than 12:00 tomorrow
                if ($timestamp < $tomorrow) {
                    return 'today';
                }
                return 'tomorrow';
            }
            return 'yesterday';
    	}
    	
    	return date($format, $timestamp);
    
    }
	
    /**
     * Returns a "humanized" time - so, if entry was today, it will say "about 16 minutes ago", "about 8 hours ago",
     * but if it isn't it will return the time formatted in $format format
     * 
     * I am sure this is not the best way to do this, but it works
     */
    public function naturalTime($timestamp = null, $format = null) {
    
    	if (is_null($timestamp)) $timestamp = time();
    	if (is_null($format)) $format = self::TIME_FORMAT;
        if (!is_numeric($timestamp)) $timestamp = strtotime($timestamp);
    	
    	$now = time();
    	$hour = 60*60;
    	if ($this->naturalDay($timestamp, $format) == 'today') {
            $hourago = $now - $hour;
            $hourfromnow = $now + $hour;
            // if timestamp passed in was after an hour ago...
            if ($timestamp > $hourago) {
                // if timestamp passed in is in the future...
                if ($timestamp > $now) {
                    // return how many minutes from now
                    $seconds = $timestamp - $now;
                    $minutes = (integer) round($seconds/60);
                    // if more than 60 minutes ago, report in hours
                    if ($minutes > 60) {
                        $hours = round($minutes/60);
                        return "in about $hours hours";
                    }
                    // if it got rounded down to zero, or it was one, report one
                    if (!$minutes || $minutes === 1) return "just now";
                    return "in about $minutes minutes";
                }
                // return how many minutes from now
                $seconds = $now - $timestamp;
                $minutes = (integer) round($seconds/60);
                // if it got rounded down to zero, or it was one, report one
                if (!$minutes || $minutes === 1) return "just now";
                return "about $minutes minutes ago";
            }
    	}
    	
    	return date($format, $timestamp);
    
    }
	
    public function naturalDayAndTime($timestamp = null, $format = null) {
    
        if (is_null($timestamp)) $timestamp = time();
        if (is_null($format)) $format = self::DAYANDTIME_FORMAT;
        if (!is_numeric($timestamp)) $timestamp = strtotime($timestamp);
        
        $day = $this->naturalDay($timestamp);
        $time = $this->naturalTime($timestamp);
        if ($day == 'today') {
            return $time;
        } elseif ($day == 'yesterday') {
            return $day . " " . $time;
        } else {
            return date($format, $timestamp);
        }
    
    }
	
    /**
     * For now, this will only convert numbers 0-9
     * @todo Convert a number to it's spelled-out version. For instance, 1 becomes one, 307 becomes
     * three hundred seven, 28 becomes twenty-eight.
     */
    public function strNum($value) {
    
        $int = (integer) $value;
        $numbers = array('zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine');
        if (isset($numbers[$value])) return $numbers[$value];
        return $value;
    
    }
	
	/**
	 * Converts bytes to their larger counterparts. So 1048576 is converted to 1MB
	 * @todo I know there is a much more clever way to do this with bitwise / binary math, but I don't know how
	 */
	public function filesize($bytes) {
	
		$kb = pow(2, 10);
		$mb = pow(2, 20);
		$gb = pow(2, 30);
		$str = '';
		if ($bytes >= $gb) {
			$gbs = round($bytes / $gb, 2);
			$rem = $bytes % $gb;
			// @todo god I'm bad at math. I'm not sure if this is even any different than just accepting $gbs
			$dec = round($rem / $gb, 2);
			return $gbs . ' GB';
		}
		if ($bytes >= $mb) {
			$mbs = round($bytes / $mb, 2);
			// $rem = $bytes % $gb;
			// @todo god I'm bad at math. I'm not sure if this is even any different than just accepting $gbs
			// $dec = round($rem / $gb, 2);
			return $mbs . ' MB';
		}
		if ($bytes >= $kb) {
			$kbs = round($bytes / $kb, 2);
			// $rem = $bytes % $gb;
			// @todo god I'm bad at math. I'm not sure if this is even any different than just accepting $gbs
			// $dec = round($rem / $gb, 2);
			return $kbs . ' KB';
		}
	
	}
	
	/**
	 * Convert decimal to roman numeral
	 * This method will convert a decimal in the range of 1 to 3,999 to a 
	 * roman numeral representation. It is impractical to convert numbers larger
	 * than that and likely wouldn't be used anyway.
	 */
	public function decimalToRoman($number) {
	
		
	
	}
	
	/**
	 * Convert roman numeral to decimal
	 * This method will convert a roman numeral in the range of 1 to 3,999 to a
	 * decimal representation. It is impractical to convert numbers larger than
	 * that and likely wouldn't be used anyway.
	 */
	public function romanToDecimal() {
	
		
	
	}

	/**
	 * Format a 7, 10, or 11 (10 plus 1 in front) digit number in a pretty human
	 * readable way.
	 */
    public function formatPhone($phone) {
    
        $length = strlen($phone);
        if ($length != 10 && $length != 7 && $length != 11) return 'Invalid Number';
        $rvrs = strrev($phone);
        $suffix = strrev(substr($rvrs, 0, 4));
        $prefix = strrev(substr($rvrs, 4, 3));
        $area = strrev(substr($rvrs, 7, 3));
        $first = strrev(substr($rvrs, 10));
        return "$first ({$area}) {$prefix}-{$suffix}";
    
    }
	
	/**
	 * Converts duration from one type to another (hours to days, minutes to hours, etc.)
	 * @todo Currently it only supports minutes to hours...
	 */
	public function duration($duration, $from, $to) {
	
		if ($from != 'minutes' || $to != 'hours') {
			throw new Visi_View_Helper_Exception('Duration conversion type(s) not yet supported.');
		}
		$hours = (integer) ($duration / 60);
		$minutes = $duration % 60;
		return sprintf('%02d:%02d', $hours, $minutes);
	
	}

}
